package com.wekj.generator;

import cn.hutool.core.util.XmlUtil;
import com.baomidou.mybatisplus.core.toolkit.ArrayUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.config.rules.IColumnType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;

import javax.annotation.PostConstruct;
import javax.xml.xpath.XPathConstants;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author: luoji
 * @Date: 2020/2/24 14:34
 * @Desc: 类描述
 */
@Component
@Slf4j
public class Generator {

    private static Settings SETTINGS = new Settings();

    private static final String TINYINT = "tinyint(1)";

    private static final String DATE = "date";

    private static final String TIME = "time";

    private static final String TIMESTAMP = "timestamp";


    @PostConstruct
    public void init() {
        //配置信息 初始化
        SETTINGS = initSettings();
        printSettingsInfo();
        generator();
    }

    private void generator() {
        log.info("开始执行代码生成器");
        //1.读取配置文件
        log.info("读取配置文件");
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        log.info("全局配置...");
        mpg.setGlobalConfig(this.getGlobalConfig());
        log.info("数据源配置...");
        mpg.setDataSource(this.getDataSourceConfig());
        log.info("包配置...");
        PackageConfig packageConfig = this.getPackageConfig();
        mpg.setPackageInfo(packageConfig);
        log.info("自定义配置...");
        mpg.setCfg(this.getInjectionConfig(packageConfig));
        log.info("模板配置...");
        mpg.setTemplate(this.getTemplateConfig());
        log.info("策略配置...");
        mpg.setStrategy(this.getStrategyConfig(packageConfig));
        log.info("模板引擎配置");
        mpg.setTemplateEngine(new VelocityTemplateEngine());
        mpg.execute();
    }

    /**
     * 全局配置
     *
     * @return GlobalConfig
     */
    private GlobalConfig getGlobalConfig() {
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor(SETTINGS.getAuthor());
        gc.setActiveRecord(SETTINGS.getActiveRecord());
        gc.setBaseResultMap(SETTINGS.getBaseResultMap());
        gc.setBaseColumnList(Boolean.TRUE);
        //是否覆盖原有目录
        gc.setFileOverride(SETTINGS.getFileOverride());
        //是否打开目录
        gc.setOpen(SETTINGS.getOpen());
        return gc;
    }

    /**
     * 数据源配置
     *
     * @return DataSourceConfig
     */
    private DataSourceConfig getDataSourceConfig() {
        DataSourceConfig dsc = new DataSourceConfig();
        // 数据源url
        dsc.setUrl(SETTINGS.getJdbcUrl());
        // 表空间
        dsc.setSchemaName(SETTINGS.getSchemaName());
        // 数据库驱动 com.mysql.jdbc.Driver
        dsc.setDriverName(SETTINGS.getDriverName());
        // 账号
        dsc.setUsername(SETTINGS.getUserName());
        // 密码
        dsc.setPassword(SETTINGS.getPassword());
        MySqlTypeConvert mySqlTypeConvert = new MySqlTypeConvert() {
            @Override
            public IColumnType processTypeConvert(GlobalConfig globalConfig, String fieldType) {
                // 自定义类型转换
                if (TINYINT.equals(fieldType.toLowerCase())) {
                    return DbColumnType.INTEGER;
                }
//                if (DATE.equals(fieldType.toLowerCase())) {
//                    return DbColumnType.DATE_SQL;
//                }
//                if (TIME.equals(fieldType.toLowerCase())) {
//                    return DbColumnType.TIME;
//                }
//                if (TIMESTAMP.equals(fieldType.toLowerCase())) {
//                    return DbColumnType.DATE;
//                }
                return super.processTypeConvert(globalConfig, fieldType);
            }
        };
        dsc.setTypeConvert(mySqlTypeConvert);
        return dsc;
    }

    private PackageConfig getPackageConfig() {
        log.info("设置包名");
        PackageConfig pc = new PackageConfig();
        // 父类包名
        pc.setParent(SETTINGS.getParent());
        // 父包模块名称
        pc.setModuleName(SETTINGS.getModuleName());
        pc.setEntity(SETTINGS.getEntity());
        pc.setXml(SETTINGS.getXml());
        pc.setMapper(SETTINGS.getMapper());
        pc.setService(SETTINGS.getService());
        pc.setServiceImpl(SETTINGS.getServiceImpl());
        pc.setController(SETTINGS.getController());
        return pc;
    }

    /**
     * 自定义配置
     *
     * @return InjectionConfig
     */
    private InjectionConfig getInjectionConfig(PackageConfig pc) {
        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };
        // 如果模板引擎是 freemarker
//        String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
         String templatePath = "/templates/mapper.xml.vm";
        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 ， 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化！！
                String projectPath = System.getProperty("user.dir");
                return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });
        cfg.setFileOutConfigList(focList);
        return cfg;
    }

    /**
     * 配置模板
     *
     * @return TemplateConfig
     */
    private TemplateConfig getTemplateConfig() {
        TemplateConfig templateConfig = new TemplateConfig();
        // 配置自定义输出模板
        //指定自定义模板路径，注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        // templateConfig.setEntity("templates/entity2.java");
        // templateConfig.setService();
        // templateConfig.setController();
        templateConfig.setXml(null);
        return templateConfig;
    }

    /**
     * 策略配置
     *
     * @return StrategyConfig
     */
    private StrategyConfig getStrategyConfig(PackageConfig pc) {
        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        //实体自己的父类 有填 没有 不填
        if (StringUtils.isNotBlank(SETTINGS.getSuperEntityClass())) {
            strategy.setSuperEntityClass(SETTINGS.getSuperEntityClass());
        }
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        // 控制器公共父类 有填没有不设置
        if (StringUtils.isNotBlank(SETTINGS.getSuperControllerClass())) {
            strategy.setSuperControllerClass(SETTINGS.getSuperControllerClass());
        }
        // 写于父类中的公共字段
//        strategy.setSuperEntityColumns("id");
        // 表名，多个用分隔符 英文逗号分隔
        strategy.setInclude(ArrayUtils.isNotEmpty(SETTINGS.getTableNameArray()) ? SETTINGS.getTableNameArray() : null);
        strategy.setControllerMappingHyphenStyle(Boolean.TRUE);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        //实体类不生成 seriaVersionUID
        strategy.setEntitySerialVersionUID(Boolean.FALSE);
        strategy.setEntityColumnConstant(Boolean.TRUE);
        //生成字段注解
        strategy.setEntityTableFieldAnnotationEnable(Boolean.TRUE);
        //乐观锁字段名称
        strategy.setVersionFieldName(null);
        //逻辑删除字段()
        strategy.setLogicDeleteFieldName(null);
        return strategy;
    }


    private static Settings initSettings() {
        Document docResult = XmlUtil.readXML(new File(System.getProperty("user.dir") + "/" + "settings.xml"));
        Settings settings = new Settings();
        settings.setAuthor((String) XmlUtil.getByXPath("//settings/author", docResult, XPathConstants.STRING));
        settings.setActiveRecord((Boolean) XmlUtil.getByXPath("//settings/activeRecord", docResult, XPathConstants.BOOLEAN));
        settings.setBaseResultMap((Boolean) XmlUtil.getByXPath("//settings/baseResultMap", docResult, XPathConstants.BOOLEAN));
        settings.setFileOverride((Boolean) XmlUtil.getByXPath("//settings/fileOverride", docResult, XPathConstants.BOOLEAN));
        settings.setOpen((Boolean) XmlUtil.getByXPath("//settings/open", docResult, XPathConstants.BOOLEAN));
        settings.setJdbcUrl((String) XmlUtil.getByXPath("//settings/jdbcUrl", docResult, XPathConstants.STRING));
        settings.setUserName((String) XmlUtil.getByXPath("//settings/userName", docResult, XPathConstants.STRING));
        settings.setPassword((String) XmlUtil.getByXPath("//settings/password", docResult, XPathConstants.STRING));
        settings.setSchemaName((String) XmlUtil.getByXPath("//settings/schemaName", docResult, XPathConstants.STRING));
        settings.setDriverName((String) XmlUtil.getByXPath("//settings/driverName", docResult, XPathConstants.STRING));
        settings.setParent((String) XmlUtil.getByXPath("//settings/parent", docResult, XPathConstants.STRING));
        settings.setModuleName((String) XmlUtil.getByXPath("//settings/moduleName", docResult, XPathConstants.STRING));
        settings.setEntity((String) XmlUtil.getByXPath("//settings/entity", docResult, XPathConstants.STRING));
        settings.setService((String) XmlUtil.getByXPath("//settings/service", docResult, XPathConstants.STRING));
        settings.setServiceImpl((String) XmlUtil.getByXPath("//settings/serviceImpl", docResult, XPathConstants.STRING));
        settings.setMapper((String) XmlUtil.getByXPath("//settings/mapper", docResult, XPathConstants.STRING));
        settings.setXml((String) XmlUtil.getByXPath("//settings/xml", docResult, XPathConstants.STRING));
        settings.setController((String) XmlUtil.getByXPath("//settings/controller", docResult, XPathConstants.STRING));
        settings.setSuperEntityClass((String) XmlUtil.getByXPath("//settings/superEntityClass", docResult, XPathConstants.STRING));
        settings.setSuperControllerClass((String) XmlUtil.getByXPath("//settings/superControllerClass", docResult, XPathConstants.STRING));
        settings.setVersionFieldName((String) XmlUtil.getByXPath("//settings/versionFieldName", docResult, XPathConstants.STRING));
        settings.setDeleteLogicFieldName((String) XmlUtil.getByXPath("//settings/deleteLogicFieldName", docResult, XPathConstants.STRING));
        String tableArray = (String) XmlUtil.getByXPath("//settings/tableNameArray", docResult, XPathConstants.STRING);
        settings.setTableNameArray(StringUtils.isNotBlank(tableArray) ? tableArray.split(",") : null);
        log.info("配置信息: settings={}",settings);
        return settings;
    }


    private void printSettingsInfo(){
        BeanInfo beanInfo;
        try{
            beanInfo= Introspector.getBeanInfo(SETTINGS.getClass(), Object.class);
            if(beanInfo!=null){
                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                for(PropertyDescriptor p:propertyDescriptors){
                    //获得属性名称
                    Object obj = new PropertyDescriptor(p.getName(), Settings.class).getReadMethod().invoke(SETTINGS);
                    log.info(p.getName() + "=" +obj);
                }
            }

        }catch (IntrospectionException | InvocationTargetException | IllegalAccessException ignored){

        }
    }
}
